/*
Copyright (C) 2010 Copyright 2010 Google Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
package jake2.gwt.client;

import jake2.qcommon.Com;
import jake2.qcommon.Compatibility;
import jake2.qcommon.ResourceLoader;
import jake2.util.StringToByteBuffer;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;

import com.google.gwt.core.client.Duration;
import com.google.gwt.user.client.Timer;
import com.google.gwt.xhr.client.ReadyStateChangeHandler;
import com.google.gwt.xhr.client.XMLHttpRequest;

public class GwtResourceLoaderImpl implements ResourceLoader.Impl {

  private static final int RECEIVED_WAIT_TIME = 1;
  
  private int freeSequenceNumber;
  private int ignoreSequenceNumbersBelow;
  private int currentSequenceNumber;
  private ArrayList<ResponseHandler> readyList = new ArrayList<ResponseHandler>();
  
  static class ResponseHandler {
    int sequenceNumber;
    ResourceLoader.Callback callback;
    String response;
    
    ResponseHandler(int sequenceNumber, ResourceLoader.Callback callback, String response) {
      this.sequenceNumber = sequenceNumber;
      this.callback = callback;
      this.response = response;
    }
  }
  
  private static StringToByteBuffer sbb = (StringToByteBuffer) (Object) ByteBuffer.allocate(1);
  
  public boolean pump() {
    return currentSequenceNumber != freeSequenceNumber;
  }

  public void reset() {
    ignoreSequenceNumbersBelow = freeSequenceNumber;
    currentSequenceNumber = freeSequenceNumber;
  }
  
  public void loadResourceAsync(final String path, final ResourceLoader.Callback callback) {
    XMLHttpRequest req = XMLHttpRequest.create();
    
    final String eol = path.endsWith(".bsp") ? "\n" : "\r";
    
    final Exception e = new Exception();
    final int mySequenceNumber = freeSequenceNumber++;
    
    req.setOnReadyStateChange(new ReadyStateChangeHandler() {
      boolean receivingMsg;
      public void onReadyStateChange(final XMLHttpRequest xhr) {
    	if (xhr.getReadyState() == 3 && !receivingMsg) {
    	  Com.Printf("Receiving #" + mySequenceNumber + ": " + path + eol);
    	  receivingMsg = true;
    	} else if (xhr.getReadyState() == 4) {
    	  if (mySequenceNumber < ignoreSequenceNumbersBelow) {
            Com.Printf("Ignoring outdated response #" + mySequenceNumber + ": " + path + "\n");
    	  } else {
    	    String response;
    	    if (xhr.getStatus() != 200) {
              Com.Printf("Failed to load file #" + currentSequenceNumber + "\n");
              Compatibility.printStackTrace(e);
              ResourceLoader.fail(new IOException("status = " + xhr.getStatus()));
              response = null;
            } else {
              response = xhr.getResponseText();
              Com.Printf("Received response #" + mySequenceNumber + ": " + path + "\r");
            }
            readyList.add(0, new ResponseHandler(mySequenceNumber, callback, response));
            if (mySequenceNumber == currentSequenceNumber) {
              processReadyList();
            }
    	  }
        }
      }
    });

    Com.Printf("Requesting: " + path + eol);

    overrideMimeType(req, "text/plain; charset=x-user-defined");
    req.open("GET", "/baseq2/" + path);
    req.send();
  }

  private native void overrideMimeType(XMLHttpRequest req, String mimeType) /*-{
    req.overrideMimeType(mimeType);
  }-*/;

  private void processReadyList() {         
    new Timer() {
      @Override
      public void run() {
        for (int i = readyList.size() - 1; i >= 0; i--) {
          ResponseHandler handler = readyList.get(i);
          if (handler.sequenceNumber == currentSequenceNumber) {
            if (handler.response != null) {
              double t0 = Duration.currentTimeMillis();
              handler.callback.onSuccess(sbb.stringToByteBuffer(handler.response));
              Com.Printf("Processed #" + currentSequenceNumber + " in " + (Duration.currentTimeMillis() - t0) / 1000.0 + "s\r");
            }
            readyList.remove(i);
            currentSequenceNumber++;
            processReadyList();
            return;
          }
        }
      }
    }.schedule(RECEIVED_WAIT_TIME);
  }
}
